package com.qzboot.backend.web.common.config;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.qzboot.backend.web.common.WebUtil;
import com.qzboot.common.support.BusinessException;
import com.qzboot.facade.entity.SysUser;
import com.qzboot.facade.service.SysLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;


/**
 * 系统日志，切面处理类
 *
 * @author zengzh
 */
@Slf4j
@Aspect
@Component
public class WebLogAspect {

    private static final String[] LOG_TYPE = {"NORMAL", "EXCEPTION"};

    @Reference
    private SysLogService sysLogService;

    @Value("${project.isDemo}")
    private Boolean isDemo;

    /**
     * 开始时间
     */
    private long startTime = 0L;

    @Pointcut("execution(* *..controller..*.*(..))")
    public void webLogPointCut() {

    }

    @Around("webLogPointCut()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        startTime = System.currentTimeMillis();
        //异常标记
        Throwable throwable = null;
        //获取请求对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        //构建日志对象
        com.qzboot.facade.entity.SysLog sysLogEntity = buildSysLog(pjp, request);

        //拼接请求日志
        StringBuffer logbf = appendLogStrPre(pjp, request);
        Object result = null;
        //演示环境不允许写操作
        String[] reqUriSplit = request.getRequestURI().split("/");
        if (isDemo && !StrUtil.startWithAny(reqUriSplit[reqUriSplit.length - 1], "list", "get", "login", "logout")) {
            throw new BusinessException("演示环境不允许操作");
        }

        try {
            result = pjp.proceed();
        } catch (Throwable e) {
            throwable = e;
            throw e;
        } finally {
            //拼接返回日志
            String logStr = appendLogStrAfter(logbf, result);
            log.info(logStr);
            //持久化日志到数据库
            if (null != sysLogEntity) {
                sysLogEntity.setUseTime(System.currentTimeMillis() - startTime);
                boolean exFlag = null != throwable;
                sysLogEntity.setLogType(exFlag ? LOG_TYPE[1] : LOG_TYPE[0]);
                sysLogEntity.setExtInfo(exFlag ? ExceptionUtil.getMessage(throwable) : null);
                sysLogService.save(sysLogEntity);
            }
        }
        return result;
    }

    private com.qzboot.facade.entity.SysLog buildSysLog(ProceedingJoinPoint pjp, HttpServletRequest request) {
        SysLog sysLog = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(SysLog.class);
        if (null == sysLog) {
            return null;
        }
        String content = sysLog.value();
        com.qzboot.facade.entity.SysLog entity = new com.qzboot.facade.entity.SysLog();
        entity.setContent(content);
        entity.setReqIp(WebUtil.getClientIP(request));
        entity.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
        entity.setReqUri(request.getRequestURI());
        entity.setMethod(request.getMethod());
        Object[] args = pjp.getArgs();
        List<String> argList = new ArrayList<>();
        for (Object o : args) {
            if (o instanceof HttpServletRequest || o instanceof HttpServletResponse) {
                //HttpServletRequest HttpServletResponse 如果进行JSONtoString会抛异常
                continue;
            }
            argList.add(JSON.toJSONString(o));
        }
        entity.setParams(CollectionUtil.join(argList, StrUtil.COMMA));
        SysUser user = (SysUser) SecurityUtils.getSubject().getPrincipal();
        if (ObjectUtil.isNotNull(user)) {
            entity.setCreateByUserId(user.getId());
            entity.setCreateByUsername(user.getUsername());
        }
        return entity;
    }

    /**
     * 记录下请求内容
     *
     * @param pjp
     * @param request
     * @return StringBuffer
     * @author zengzh
     */
    private StringBuffer appendLogStrPre(ProceedingJoinPoint pjp, HttpServletRequest request) {
        startTime = System.currentTimeMillis();
        StringBuffer logbf = new StringBuffer();
        logbf.append("URL:").append(request.getRequestURL());
        logbf.append("\t,HTTP_METHOD:").append(request.getMethod());
        logbf.append(",IP:").append(WebUtil.getClientIP(request));
        logbf.append(",CLASS_METHOD:").append(pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName());
        logbf.append(",ARGS:");
        Object[] args = pjp.getArgs();
        for (Object o : args) {
            if (o instanceof HttpServletRequest || o instanceof HttpServletResponse) {
                //HttpServletRequest HttpServletResponse 如果进行JSONtoString会抛异常
                continue;
            }
            logbf.append(JSON.toJSONString(o));
        }
        return logbf;
    }

    /**
     * 处理完请求，返回内容
     *
     * @param logbf
     * @param result
     * @return String
     * @author zengzh
     */
    private String appendLogStrAfter(StringBuffer logbf, Object result) {
        logbf.append(",RESPONSE:").append(JSON.toJSONString(result));
        logbf.append(",START_TIME:").append(DateUtil.date(startTime));
        logbf.append(",SEPEND_TIME:").append(System.currentTimeMillis() - startTime + "ms}");
        return logbf.toString();
    }
}
